import datetime
import traceback
from abc import ABC
from inspect import isfunction

from flow_alchemy.util.serializable import Serializable
from flow_alchemy.util.__init__ import local_time


# Python 3.6 开始字典有序并效率更高
def snapshot(obj):
    if isinstance(obj, datetime.datetime):
        return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
    elif isinstance(obj, datetime.date):
        return obj.strftime('%Y-%m-%d')
    elif isinstance(obj, datetime.time):
        return obj.isoformat()
    elif isinstance(obj, Serializable):
        func = getattr(obj, 'to_dict')
        return snapshot(func())
    elif isinstance(obj, tuple):
        return list(map(snapshot, obj))
    elif isinstance(obj, list):
        return list(map(snapshot, obj))
    elif isinstance(obj, dict):
        return dict(map(lambda x: (x, snapshot(obj[x])), obj))
    elif isfunction(obj):
        return "function %s" % getattr(obj, '__name__')
    elif isinstance(obj, Exception):
        return traceback.format_exc()
    return obj


#
# class LogBase(Serializable, ABC):
#     def __init__(self, name, manage):
#         self.name = name
#         self.manage = manage
#         self._begin = None
#         self._end = None
#
#     def __enter__(self):
#         self.begin()
#         return self
#
#     def __exit__(self, exc_type, exc_value, exc_traceback):
#         self.end()
#         self.manage.pop()
#         if exc_type and exc_value and exc_traceback:
#             with self.manage.make_item(name=str(exc_type)) as o:
#                 o.add(exc_type=str(), exc_value=exc_value, traceback=traceback.format_exc())
#             raise Exception(exc_value)
#         return True
#
#     def begin(self):
#         self._begin = local_time()
#
#     def end(self):
#         self._end = local_time()
#
#     def add(self, **kwargs):
#         raise NotImplementedError("Object %s method %s is not implemented" % (self.__class__.__name__, "add"))
#
#
# class LogItem(LogBase):
#     def __init__(self, name, manage):
#         super(LogItem, self).__init__(name, manage)
#         self.keys = []
#         self.values = []
#
#     def add(self, **kwargs):
#         for k, v in kwargs.items():
#             self.keys.append(k)
#             self.values.append(snapshot(v))
#
#     def to_dict(self):
#         return snapshot(
#             {'name': self.name, 'keys': self.keys, 'values': self.values, 'type': self.__class__.__name__,
#              'begin': self._begin,
#              'end': self._end})
#
#
# class SetattrItem(LogItem):
#     def __init__(self, obj, manage):
#         super(LogItem, self).__init__('Setattr', manage)
#         self.obj = obj
#         self.old_data = None
#         self.new_data = None
#         self.data = {}
#         self.counter = 0
#
#     def add(self, attr, new_value):
#         self.counter += 1
#         self.data[str(self.counter)] = (attr, snapshot(new_value))
#
#     def to_dict(self):
#         return snapshot(
#             {'name': self.name, 'old_data':self.old_data,'new_data':self.new_data,'data': self.data, 'type': self.__class__.__name__, 'begin': self._begin,
#              'end': self._end})
#
#     def begin(self):
#         super(SetattrItem, self).begin()
#         self.old_data = str(self.obj)
#
#     def end(self):
#         super(SetattrItem, self).end()
#         self.new_data = str(self.obj)
#
#
# class FunctionItem(LogItem):
#
#     def add(self, **kwargs):
#         for k, v in kwargs.items():
#             # if k in ['args', 'kwargs', 'result'] and v:
#             setattr(self, k, v)
#
#     def to_dict(self):
#         return snapshot(
#             {'name': self.name,
#              'args': getattr(self, 'args', None),
#              'kwargs': getattr(self, 'kwargs', None),
#              'return': getattr(self, 'result', None),
#              'type': self.__class__.__name__,
#              'begin': self._begin,
#              'end': self._end})


class LogItem(Serializable, ABC):
    def __init__(self, name, manage, t='LogItem'):
        self.name = name
        self.manage = manage
        self.begin_date = None
        self.end_date = None
        # self.counter = 0
        self.data = []
        self._type = t

    def __enter__(self):
        self.begin()
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.end()
        self.manage.pop()
        if exc_type and exc_value and exc_traceback:
            with self.manage.make_item(name=str(exc_type)) as o:
                o.add(exc_type=str(), exc_value=exc_value, exc_traceback=traceback.format_exc())
            # raise Exception(exc_value)
        return True

    def begin(self):
        self.begin_date = local_time()

    def end(self):
        self.end_date = local_time()

    def add(self, **kwargs):
        for k, v in kwargs.items():
            self.add_pair(k, v)

    def add_pair(self, k, v):
        # self.counter += 1
        self.data.append([str(k), snapshot(v)])

    def to_dict(self):
        return {'name': self.name,
                'data': snapshot(self.data),
                'type': self._type,
                'begin': snapshot(self.begin_date),
                'end': snapshot(self.end_date)
                }

    @classmethod
    def snapshot(cls, onj):
        return snapshot(onj)


class SetattrItem(LogItem):
    def __init__(self, model, manage):
        super(SetattrItem, self).__init__(model.short_name(), manage, 'SetattrItem')
        self.model = model

    def begin(self):
        super(SetattrItem, self).begin()
        self.add_pair('old_data', str(self.model))

    def end(self):
        super(SetattrItem, self).end()
        self.add_pair('new_data', str(self.model))

    def add_pair(self, k, v):
        # self.counter += 1
        self.data.append([str(k), snapshot(v)])


